package com.smartalarmclock.Model;

import java.util.EnumSet;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.smartalarmclock.Model.Reference.DeleteMethod;
import com.smartalarmclock.Model.Exceptions.DataEntityDoesNotExistException;
import com.smartalarmclock.Model.Exceptions.InvalidDeleteDataEntityOperationException;
import com.smartalarmclock.Model.Exceptions.InvalidNullReferenceOnDeleteException;
import com.smartalarmclock.Model.Exceptions.InvalidReferenceToRemoveException;
import com.smartalarmclock.Model.Exceptions.InvalidSaveDataEntityOperationException;
import com.smartalarmclock.Model.Exceptions.ReferenceCannotBeNullException;

final class AutoResponse extends ReferencedDataEntity implements IAutoResponse {
	
	// Database Constants
	
	public static final String AutoResponseTableName = "AutoResponse";
	
	private static final String c_AutoResponseColumnNameSettings = "Settings";
	private static final String c_AutoResponseColumnNameSMSToSend = "SMSToSend";
	
	private static final String[] c_QueryAllColumns = new String[] { c_ColumnNameId, c_AutoResponseColumnNameSettings, c_AutoResponseColumnNameSMSToSend };
	
	// Members
	
	private static final boolean c_DefaultBlockingStatus = false;
	private static final boolean c_DefaultSendToAnsweringMachineStatus = false; 
	
	private EnumSet<Settings> m_Settings;
	private final SingleReference<ISMSMessage> m_SMSMessageReference;
	
	// Constructors
	
	/**
	 * Constructor
	 * @param isBlocking whether the auto response should block the call
	 * @param shouldSendToAnsweringMachine whether the call should be sent to the answering machine
	 * @param smsToSend the sms messsage to send automatically
	 */
	protected AutoResponse(boolean isBlocking, boolean shouldSendToAnsweringMachine, ISMSMessage smsToSend) {
		super();
		m_SMSMessageReference = new SingleReference<ISMSMessage>(this, smsToSend, true, SMSMessage.class, DeleteMethod.NoAction);
		Initialize(isBlocking, shouldSendToAnsweringMachine, smsToSend);
	}
	
	/**
	 * Constructor which uses the default values
	 */
	protected AutoResponse() {
		super();
		m_SMSMessageReference = new SingleReference<ISMSMessage>(this, SMSMessage.class, DeleteMethod.NoAction);
		Initialize(c_DefaultBlockingStatus, c_DefaultSendToAnsweringMachineStatus, null);
	}
	
	/**
	 * Constructor
	 * @param id the instance id
	 * @param isBlocking whether the auto response should block the call
	 * @param shouldSendToAnsweringMachine whether the call should be sent to the answering machine
	 * @param smsToSend the sms messsage to send automatically
	 */
	private AutoResponse(int id, boolean isBlocking, boolean shouldSendToAnsweringMachine, ISMSMessage smsToSend) {
		super(id);
		m_SMSMessageReference = new SingleReference<ISMSMessage>(this, smsToSend, true, SMSMessage.class, DeleteMethod.NoAction);
		Initialize(isBlocking, shouldSendToAnsweringMachine, smsToSend);
	}
	
	// IAutoResponse implementation

	/**
	 * @return whether the auto response should block the call
	 */
	@Override
	public boolean IsBlocking() {
		return m_Settings.contains(Settings.Block);
	}

	/**
	 * Sets the blocking state 
	 * @param isBlocking whether the call should be blocked or not
	 */
	@Override
	public void SetBlocking(boolean isBlocking) {
		if (isBlocking) {
			if (!m_Settings.contains(Settings.Block))
			{
				m_Settings.add(Settings.Block);
		}
		}
		else if (m_Settings.contains(Settings.Block)){
			m_Settings.remove(Settings.Block);
		}
		SetUpdated();
	}

	/**
	 * @return whether the auto response should send the call to the answering machine
	 */
	@Override
	public boolean IsSendingToAnsweringMachine() {
		return m_Settings.contains(Settings.GoToAnsweringMachine);
	}

	/**
	 * Sets the answering machine state 
	 * @param isSendingToAnsweringMachine whether the call should be sent to answering machine.
	 */
	@Override
	public void SetSendToAnsweringMachine(boolean isSendingToAnsweringMachine) {
		if (isSendingToAnsweringMachine) {
			m_Settings.contains(Settings.GoToAnsweringMachine);
		}
		else {
			m_Settings.remove(Settings.GoToAnsweringMachine);
		}
		SetUpdated();
	}

	/**
	 * @return whether the auto response should send an automated SMS
	 */
	@Override
	public boolean IsSendingAutoSMS() {
		return m_Settings.contains(Settings.SendAutoSMS);
	}

	/**
	 * @return the automated SMS to send
	 */
	@Override
	public ISMSMessage GetAutoSMS() {
		return m_SMSMessageReference.GetReference();
	}

	/**
	 * Sets the sms message to send automatically 
	 * @param message the sms message to send
	 */
	@Override
	public void SetSmsToSend(ISMSMessage message) {
		if (message != null) {
			m_Settings.contains(Settings.SendAutoSMS);
		}
		else {
			m_Settings.remove(Settings.SendAutoSMS);
		}
		
		try {
		m_SMSMessageReference.Set(message);
		} catch (ReferenceCannotBeNullException e) {
			// The reference can be null
		}
		
		SetUpdated();
	}
	
	// Public Methods
	
	/**
	 * @return the CREATE TABLE statement of the AutoResponse table
	 */
	static String GetSqlStatementCreateAutoResponseTable() {
		return "CREATE TABLE " + AutoResponseTableName + " (" +
				c_ColumnNameId + 						" INTEGER PRIMARY KEY, " +
				c_AutoResponseColumnNameSettings + 		" INTEGER NOT NULL, " +
				c_AutoResponseColumnNameSMSToSend + 	" INTEGER, " +
				"FOREIGN KEY(" + c_AutoResponseColumnNameSMSToSend + ") REFERENCES " + SMSMessage.SMSMessageTableName + "(" + c_ColumnNameId + "))";
	}
	
	/**
	 * @return the DROP TABLE statement of the AutoResponse table
	 */
	static String GetSqlStatementDropAutoResponseTable() {
		return "DROP TABLE IF EXISTS " + AutoResponseTableName;
	}
	
	/**
	 * Queries the database for an AutoResponse instance with the matching id
	 * @param context the context of the request
	 * @param id the id to query
	 * @return the matching AutoResponse instance
	 * @throws DataEntityDoesNotExistException if the data entity does not exist
	 */
	static AutoResponse GetAutoResponseById(Context context, int id) throws DataEntityDoesNotExistException {
		return GetSingleAutoResponse(
			context,
			c_ColumnNameId + "=" + id);
	}
	
	/**
	 * Queries the database for an AutoResponse instance with the matching id
	 * @param context the context of the request
	 * @param id the id to query
	 * @return the matching AutoResponse instance, or null it it does not exist
	 */
	static AutoResponse TryGetAutoResponseById(Context context, int id) {
		try {
			return GetSingleAutoResponse(context, c_ColumnNameId + "=" + id);
		} catch (DataEntityDoesNotExistException e) {
			return null;
		}
	}
	
	/**
	 * Queries the database for AutoResponse instances using the given SMS message
	 * @param context the context of the request
	 * @param message the sms message to query
	 * @return a set of matching AutoResponse instances
	 */
	static Set<IAutoResponse> GetAutoResponsesBySMSMessage(Context context, ISMSMessage message) {
		SQLiteDatabase db = OpenDBForReading(context);
		Cursor cursor = db.query(
			AutoResponseTableName,
			c_QueryAllColumns,
			c_AutoResponseColumnNameSMSToSend + "=" + message.GetId(),
			null, null, null, null);
		
		HashSet<IAutoResponse> autoResponses = new HashSet<IAutoResponse>();
		
		while (cursor.moveToNext()) {
			autoResponses.add(CreateAutoResponseFromCursor(context, cursor));
		}
		
		cursor.close();
		db.close();
		return autoResponses;
	}
	
	// Private Methods
	
	/**
	 * Queries the database for an AutoResponse instance using the given where clause
	 * @param context the context of the request
	 * @param whereClause the where clause to use
	 * @return the matching AutoResponse instance
	 * @throws DataEntityDoesNotExistException if the data entity does not exist
	 */
	private static AutoResponse GetSingleAutoResponse(Context context, String whereClause) throws DataEntityDoesNotExistException {
		SQLiteDatabase db = OpenDBForReading(context);
		Cursor cursor = db.query(
			AutoResponseTableName,
			c_QueryAllColumns,
			whereClause,
			null, null, null, null);
		
		try {
			if (!cursor.moveToFirst()) {
				throw new DataEntityDoesNotExistException(AutoResponse.class, whereClause);
			}
			
			return CreateAutoResponseFromCursor(context, cursor);
		}
		finally {
			cursor.close();
			db.close();
		}
	}
	
	/**
	 * Creates a new AutoResponse instance using the values in the cursor
	 * @param context the context of the request
	 * @param cursor the cursor with the values of the AutoResponse
	 * @return the matching instance
	 */
	private static AutoResponse CreateAutoResponseFromCursor(Context context, Cursor cursor) {
		int id = cursor.getInt(cursor.getColumnIndex(c_ColumnNameId));
		
		int settingsValue = cursor.getInt(cursor.getColumnIndex(c_AutoResponseColumnNameSettings));
		EnumSet<Settings> settings = GetSettingsFromValue(settingsValue);
		
		int smsMessageId = cursor.getInt(cursor.getColumnIndex(c_AutoResponseColumnNameSMSToSend));
		ISMSMessage message = new SMSMessageFactory().TryGetSMSMessageById(context, smsMessageId);
		
		return new AutoResponse(id, settings.contains(Settings.Block), settings.contains(Settings.GoToAnsweringMachine), message);
	}
	
	/**
	 * Initializes the new instance.
	 * @param isBlocking whether the auto response should block the call
	 * @param shouldSendToAnsweringMachine whether the call should be sent to the answering machine
	 * @param smsToSend the sms messsage to send automatically
	 */
	private void Initialize(boolean isBlocking,	boolean shouldSendToAnsweringMachine, ISMSMessage smsToSend) {
		m_Settings = EnumSet.noneOf(Settings.class);
		if (isBlocking) {
			m_Settings.add(Settings.Block);
		}
		
		if (shouldSendToAnsweringMachine) {
			m_Settings.add(Settings.GoToAnsweringMachine);
		}
		
		if (smsToSend != null) {
			m_Settings.add(Settings.SendAutoSMS);
		}
		
		try {
			m_SMSMessageReference.Set(smsToSend);
		} catch (ReferenceCannotBeNullException e) { }
	}
	
	/**
	 * @param value the value of the settings (bitwise)
	 * @return the matching Settings enum set with the matching values
	 */
	private static EnumSet<Settings> GetSettingsFromValue(int value) {
		EnumSet<Settings> set = EnumSet.noneOf(Settings.class);
		
		if ((value & Settings.Block.GetValue()) != 0) {
			set.add(Settings.Block);
		}
		
		if ((value & Settings.GoToAnsweringMachine.GetValue()) != 0) {
			set.add(Settings.GoToAnsweringMachine);
		}
		
		if ((value & Settings.SendAutoSMS.GetValue()) != 0) {
			set.add(Settings.SendAutoSMS);
		}
		
		return set;
	}
	
	/**
	 * @return the numeric value of all the settings (bitwise or of all the values)
	 */
	private int GetSettingsValue() {
		int value = 0;
		
		for (Settings setting : m_Settings) {
			value = value | setting.GetValue();
		}
		
		return value;
	}
	
	// IDataEntity implementation

	@Override
	public void Refresh(Context context) throws DataEntityDoesNotExistException {
		AutoResponse tempAutoResponse = GetAutoResponseById(context, GetId());
		Initialize(tempAutoResponse.IsBlocking(), tempAutoResponse.IsSendingToAnsweringMachine(), tempAutoResponse.GetAutoSMS());
	}
	
	@Override
	public void Save(Context context) throws InvalidSaveDataEntityOperationException {
		if (IsUpdated()) {
			// Saving the SMS message object before adding it to the auto response in the DB.
			m_SMSMessageReference.SaveReferences(context);
			
			super.Save(context);
		}
	}
	
	/**
	 * Deletes the instance from the database
	 * @param context the context of the request
	 * @throws InvalidDeleteDataEntityOperationException if the data entity is referenced and cannot be deleted
	 */
	@Override
	public void Delete(Context context) throws InvalidDeleteDataEntityOperationException, InvalidSaveDataEntityOperationException {
		super.Delete(context);
		
		// Handling the deletion of the SMS messge reference.
		m_SMSMessageReference.OnDeleteReferencingEntity(context);
	}
	
	// DataEntity implementation

	@Override
	protected Hashtable<String, ContentValues> GetRecordValues() {
		ContentValues values = new ContentValues();
		
		values.put(c_AutoResponseColumnNameSettings, GetSettingsValue());
		
		ISMSMessage message = m_SMSMessageReference.GetReference();
		if (message != null) {
			values.put(c_AutoResponseColumnNameSMSToSend, message.GetId());
		}
		
		Hashtable<String, ContentValues> valuesTable = new Hashtable<String, ContentValues>();
		valuesTable.put(AutoResponseTableName, values);
		return valuesTable;
	}

	@Override
	protected Set<String> GetTableNames() {
		Set<String> tableNames = new HashSet<String>();
		tableNames.add(AutoResponseTableName);
		return tableNames;
	}
	
	// IReferencingDataEntity

	@Override
	public boolean IsReferenced(Context context, IReferencedDataEntity referencedEntity) {
		return referencedEntity == m_SMSMessageReference.GetReference();
	}

	@Override
	public boolean CanRemoveReference(Context context, IReferencedDataEntity referencedEntity) {
		return IsReferenced(context, referencedEntity);
	}

	@Override
	public void RemoveReference(Context context, IReferencedDataEntity referencedEntity) throws InvalidNullReferenceOnDeleteException, InvalidReferenceToRemoveException {
		if (IsReferenced(context, referencedEntity)) {
			SetSmsToSend(null);
		}
		else {
			throw new InvalidReferenceToRemoveException(getClass(), GetId(), referencedEntity.getClass(), referencedEntity.GetId());
		}
	}
}
